package uk.ac.nott.cs.g53dia.bee;

import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import uk.ac.nott.cs.g53dia.CommunicateAction;
import uk.ac.nott.cs.g53dia.Environment;
import uk.ac.nott.cs.g53dia.Flower;
import utils.ComparatorFactory;

public class BeeYieldApproximator {

  public static final int PRECISION = 5; // greater value -> lower precision
  public static final int DELAY_FACTOR = 50; // greater value -> lower influence

  private static final int UNKNOWN = 1;

  private List<Map<Long, Integer>> samples;
  private float[] weights;

  public BeeYieldApproximator(float[] weights, List<Map<Long, Integer>> samples) {
    this.weights = weights;
    this.samples = samples;
  }

  public int addSamples(List<Map<Long, Integer>> samples) {
    int sum = CommunicateAction.MIN_PRICE;
    for (int i = 0; i < samples.size(); i++) {
      Map<Long, Integer> flowerSamples = new HashMap<Long, Integer>(samples.get(i));
      flowerSamples.keySet().removeAll(this.samples.get(i).keySet());
      sum += flowerSamples.size();
      this.samples.get(i).putAll(samples.get(i));
    }
    return sum > CommunicateAction.MAX_SAMPLES_PRICE ? CommunicateAction.MAX_SAMPLES_PRICE : sum;
  }

  public void addSample(int flowerType, long timestep, int amount) {
    this.samples.get(flowerType).put(timestep / PRECISION, amount);
  }

  public List<Float> guessYields(final long timestep) {
    return new ArrayList<Float>() {

      {
        for (int i = 0; i < Flower.NUM_TYPES; i++) {
          add(guessYield(timestep, i) + (float) getRange(timestep, i) / DELAY_FACTOR);
        }
      }
    };
  }

  private int getRange(long timestep, int flowerType) {
    long timestep2 = timestep / PRECISION;
    List<Long> knownTimesteps = new ArrayList<Long>(samples.get(flowerType).keySet());
    if (knownTimesteps.contains(timestep2)) {
      return 0;
    }
    if (knownTimesteps.size() == 0) { // no records
      return (int) timestep2;
    } else if (knownTimesteps.size() == 1) {
      return (int) Math.abs(timestep2 - knownTimesteps.get(0));
    }
    List<Long> knownTimestepsGreaterThan = new ArrayList<Long>(knownTimesteps.size());
    List<Long> knownTimestepsLowerThan = new ArrayList<Long>(knownTimesteps.size());
    for (long tstep : knownTimesteps) {
      if (tstep > timestep2) {
        knownTimestepsGreaterThan.add(tstep);
      } else {
        knownTimestepsLowerThan.add(tstep);
      }
    }
    long greater = 0;
    long lower = 0;
    if (knownTimestepsGreaterThan.size() == 0) { // all are lower
      return (int) Math.abs(timestep2
          - Collections.min(knownTimestepsLowerThan, ComparatorFactory.getCloseLongComparatorRelativeTo(timestep2)));
    } else if (knownTimestepsLowerThan.size() == 0) { // all are greater
      return (int) Math.abs(timestep2
          - Collections.min(knownTimestepsGreaterThan, ComparatorFactory.getCloseLongComparatorRelativeTo(timestep2)));
    } else {
      greater = Collections.min(knownTimestepsGreaterThan,
          ComparatorFactory.getCloseLongComparatorRelativeTo(timestep2));
      lower = Collections.min(knownTimestepsLowerThan, ComparatorFactory.getCloseLongComparatorRelativeTo(timestep2));
      return (int) Math.min(Math.abs(timestep2 - greater), Math.abs(timestep2 - lower));
    }
  }

  public int guessYield(long timestep, int flowerType) {
    long timestep2 = timestep / PRECISION;
    // long timestep2 = timestep;
    List<Long> knownTimesteps = new ArrayList<Long>(samples.get(flowerType).keySet());
    if (knownTimesteps.contains(timestep2)) {
      return samples.get(flowerType).get(timestep2);
    }
    if (knownTimesteps.size() == 0) {
      return UNKNOWN;
    } else if (knownTimesteps.size() == 1) {
      return samples.get(flowerType).get(knownTimesteps.get(0));
    }
    List<Long> knownTimestepsGreaterThan = new ArrayList<Long>(knownTimesteps.size());
    List<Long> knownTimestepsLowerThan = new ArrayList<Long>(knownTimesteps.size());
    for (long tstep : knownTimesteps) {
      if (tstep > timestep2) {
        knownTimestepsGreaterThan.add(tstep);
      } else {
        knownTimestepsLowerThan.add(tstep);
      }
    }
    long greater = 0;
    long lower = 0;
    if (knownTimestepsGreaterThan.size() == 0) {
      Collections.sort(knownTimestepsLowerThan, ComparatorFactory.getCloseLongComparatorRelativeTo(timestep2));
      greater = knownTimestepsLowerThan.get(0);
      lower = knownTimestepsLowerThan.get(1);
    } else if (knownTimestepsLowerThan.size() == 0) {
      Collections.sort(knownTimestepsGreaterThan, ComparatorFactory.getCloseLongComparatorRelativeTo(timestep2));
      greater = knownTimestepsGreaterThan.get(1);
      lower = knownTimestepsGreaterThan.get(0);
    } else {
      greater = Collections.min(knownTimestepsGreaterThan,
          ComparatorFactory.getCloseLongComparatorRelativeTo(timestep2));
      lower = Collections.min(knownTimestepsLowerThan, ComparatorFactory.getCloseLongComparatorRelativeTo(timestep2));
    }

    int greaterValue = samples.get(flowerType).get(greater);
    int lowerValue = samples.get(flowerType).get(lower);

    float k = (float) Math.abs(greaterValue - lowerValue) / Math.abs(greater - lower);
    float q = (float) greaterValue - (k * greater);
    return Math.round(k * timestep2 + q);
  }
  
  public int getBetterFlower(long timestep, int actualYield) {
    int max = 0;
    int index = -1;
    for (int i = 0; i < Flower.NUM_TYPES; i++) {
      int yield = guessYield(timestep, i);
      if (yield > max) {
        max = yield;
        index = i;
      }
    }
    return max - actualYield > 3?index:-1;
  }

  public float howClever(long timestep) {
    return ((float) samples.get(0).size() + samples.get(1).size() + samples.get(2).size() + samples.get(3).size() + samples.get(
        4)
        .size())
        / (Flower.NUM_TYPES * ((float) Environment.DAY_LENGTH / PRECISION));
  }

}
